package com.smile.frame.workflow.flowable.demo.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smile.frame.workflow.flowable.demo.dto.BpmModelCreateReqDTO;
import com.smile.frame.workflow.flowable.demo.dto.BpmModelMetaInfoRespDTO;
import com.smile.frame.workflow.flowable.demo.dto.BpmModelUpdateReqDTO;
import com.smile.frame.workflow.flowable.demo.service.BpmProcessDefinitionService;
import com.smile.frame.workflow.flowable.demo.service.BpmnModelService;
import com.smile.frame.workflow.flowable.demo.util.BpmnModelUtils;
import com.smile.frame.workflow.flowable.demo.util.ValidationUtils;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author LGC
 */
@Slf4j
@Service
public class BpmnModelServiceImpl implements BpmnModelService {
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;

    @Override
    public void queryModelPage(String tenantId, String key, String name, String category, Integer pageNo, Integer pageSize) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        if (StrUtil.isNotBlank(tenantId)) {
            modelQuery.modelTenantId(tenantId);
        }
        if (StrUtil.isNotBlank(key)) {
            modelQuery.modelKey(key);
        }
        if (StrUtil.isNotBlank(name)) {
            modelQuery.modelNameLike("%" + name + "%"); // 模糊匹配
        }
        if (StrUtil.isNotBlank(category)) {
            modelQuery.modelCategory(category);
        }

        // 执行查询
        long count = modelQuery.count();
        if (count == 0) {
            log.info("分页查询流程模型:null");
            return;
        }
        List<Model> models = modelQuery
                .orderByCreateTime().desc()
                .listPage((pageNo - 1) * pageSize, pageSize);
        log.info("分页查询流程模型:{}", JSONUtil.toJsonStr(models));
    }


    private String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo, String description) {
        if (metaInfo == null) {
            metaInfo = new BpmModelMetaInfoRespDTO();
        }
        if (StrUtil.isNotEmpty(description)) {
            metaInfo.setDescription(description);
        }

        return JSONUtil.toJsonStr(metaInfo);
    }

    private void saveModelBpmnXml(String modelId, String bpmnXml) {
        if (StrUtil.isEmpty(bpmnXml)) {
            return;
        }
        repositoryService.addModelEditorSource(modelId, StrUtil.utf8Bytes(bpmnXml));
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createModel(BpmModelCreateReqDTO BpmModelCreateReq, String bpmnXml) {
        final String act = "【创建流程模型】 ";
        log.info(act + "start");
        if (!ValidationUtils.isXmlNCName(BpmModelCreateReq.getKey())) {
            throw new RuntimeException("流程标识格式不正确，需要以字母或下划线开头，后接任意字母、数字、中划线、下划线、句点！");
        }
        Model keyModel = repositoryService.createModelQuery().modelKey(BpmModelCreateReq.getKey()).singleResult();
        if (keyModel != null) {
            throw new RuntimeException("校验流程标识已经存在");
        }

        Model model = repositoryService.newModel();
        // 租户
        model.setTenantId(null);
        model.setName(BpmModelCreateReq.getName());
        model.setKey(BpmModelCreateReq.getKey());
        model.setCategory(BpmModelCreateReq.getCategory());
        model.setMetaInfo(buildMetaInfoStr(null, BpmModelCreateReq.getDescription()));
        // 保存流程模型定义
        repositoryService.saveModel(model);
        String modelId = model.getId();
        // 保存 BPMN XML
        saveModelBpmnXml(modelId, bpmnXml);
        log.info(act + "end");
        return modelId;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateModel(BpmModelUpdateReqDTO bpmModelUpdateReq) {
        final String act = "【更新流程模型】 ";
        log.info(act + "start");
        // 校验流程模型存在
        Model model = repositoryService.getModel(bpmModelUpdateReq.getId());
        if (model == null) {
            log.error("流程模型不存在");
            throw new RuntimeException("流程模型不存在");
        }
        model.setName(bpmModelUpdateReq.getName());
        model.setCategory(bpmModelUpdateReq.getCategory());
        model.setMetaInfo(buildMetaInfoStr(JSONUtil.toBean(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class), bpmModelUpdateReq.getDescription()));
        // 更新保存模型
        repositoryService.saveModel(model);
        // 更新保存 BPMN XML
        saveModelBpmnXml(model.getId(), bpmModelUpdateReq.getBpmnXml());
        log.info(act + "end");
    }

    @Override
    public Model getModel(String modelId) {
        return repositoryService.getModel(modelId);
    }

    @Override
    public byte[] getModelBpmnXML(String modelId) {
        return repositoryService.getModelEditorSource(modelId);
    }

    private void validateBpmnXml(byte[] bpmnBytes) {
        BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
        if (bpmnModel == null) {
            throw new RuntimeException("流程模型不存在");
        }
        // 1. 没有 StartEvent
        StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel);
        if (startEvent == null) {
            throw new RuntimeException("部署流程失败，原因：BPMN 流程图中，没有开始事件");
        }
        // 2. 校验 UserTask 的 name 都配置了
        List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
        userTasks.forEach(userTask -> {
            if (StrUtil.isEmpty(userTask.getName())) {
                throw new RuntimeException(String.format("部署流程失败，原因：BPMN 流程图中，用户任务(%s)的名字不存在", userTask.getId()));
            }
        });
    }


    /**
     * 挂起 deploymentId 对应的流程定义
     * 注意：这里一个 deploymentId 只关联一个流程定义
     *
     * @param deploymentId 流程发布Id
     */
    private void updateProcessDefinitionSuspended(String deploymentId) {
        if (StrUtil.isEmpty(deploymentId)) {
            return;
        }
        ProcessDefinition oldDefinition = bpmProcessDefinitionService.getProcessDefinitionByDeploymentId(deploymentId);
        if (oldDefinition == null) {
            return;
        }
        bpmProcessDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deployModel(String modelId) {
        log.info("start部署模型");
        // 校验流程模型存在
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtils.isEmpty(model)) {
            throw new RuntimeException("流程模型不存在");
        }
        // 校验流程图
        byte[] bpmnBytes = repositoryService.getModelEditorSource(model.getId());
        validateBpmnXml(bpmnBytes);

        // 2.1 创建流程定义
        String definitionId = bpmProcessDefinitionService.createProcessDefinition(model, bpmnBytes);

        // 2.2 将老的流程定义进行挂起。也就是说，只有最新部署的流程定义，才可以发起任务。
        updateProcessDefinitionSuspended(model.getDeploymentId());

        // 2.3 更新 model 的 deploymentId
        ProcessDefinition definition = repositoryService.getProcessDefinition(definitionId);
        model.setDeploymentId(definition.getDeploymentId());
        repositoryService.saveModel(model);
        log.info("end部署模型");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteModel(String modelId) {
        // 校验流程模型存在
        Model model = getModel(modelId);
        if (model == null) {
            throw new RuntimeException("流程模型不存在");
        }
        // 执行删除
        repositoryService.deleteModel(modelId);
        // 禁用流程定义
        updateProcessDefinitionSuspended(model.getDeploymentId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateModelState(String modelId, Integer state) {
        // 1.1 校验流程模型存在
        Model model = getModel(modelId);
        if (model == null) {
            throw new RuntimeException("流程模型不存在");
        }
        // 1.2 校验流程定义存在
        ProcessDefinition definition = bpmProcessDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
        if (definition == null) {
            throw new RuntimeException("流程定义不存在");
        }
        // 2. 更新状态
        bpmProcessDefinitionService.updateProcessDefinitionState(definition.getId(), state);

    }

    @Override
    public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) {
        return repositoryService.getBpmnModel(processDefinitionId);
    }
}
